﻿using System;
using Microsoft.Build.Utilities;
using Microsoft.Build.Framework;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.ComponentModel;

namespace RunAllTestsInSolutionTask
{
    public class RunTestsWithCoverage : Task
    {
        static Logger _log;
        [Required]
        public string FileName
        {
            get;
            set;
        }

        [Required]
        public string PerformanceToolsDir
        {
            get;
            set;
        }

        [Required]
        public string OutputDirectory
        {
            get;
            set;
        }

        public string Parmaters
        {
            get;
            set;
        }


        private Process RunProcessAsynch(ProcessStartInfo info)
        {
            var proc = new Process();
            proc.StartInfo = info;
            proc.StartInfo.UseShellExecute = false;
            proc.StartInfo.RedirectStandardOutput = true;

            proc.Start();
            proc.BeginOutputReadLine();
            proc.OutputDataReceived += new DataReceivedEventHandler(this.PrintToConsole);
            return proc;
        }

        private int RunProcessSynch(ProcessStartInfo info)
        {
            Process proc = RunProcessAsynch(info);
            proc.WaitForExit();
            return proc.ExitCode;
        }

        private void InstrumentTarget()
        {
            try
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = Path.Combine(this.PerformanceToolsDir, "vsinstr.exe");
                info.Arguments = String.Format("/coverage {0} /OUTPUTPATH:{1}", this.FileName,this.OutputDirectory);

                Log.LogMessage("Instrumenting Target:{0} {1}",
                                      info.FileName,
                                      info.Arguments);

                var exitCode = RunProcessSynch(info);
                if (exitCode != 0)
                    throw new ApplicationException("Failed to Instrument File");
            }
            catch (Win32Exception)
            {
                Log.LogError("Couldnt locate vsinstr.exe. Check PerformanceToolsDir Setting");
                throw;
            }
        }

        private void StartPerfMonitor()
        {
            try
            {
                ProcessStartInfo info = new ProcessStartInfo();
                info.FileName = Path.Combine(this.PerformanceToolsDir, "vsperfmon.exe");
                var outputFile = Path.Combine(this.OutputDirectory, Path.GetFileName(this.FileName)) + ".coverage";
                info.Arguments = String.Format("-coverage -output:{0}", outputFile);

                Log.LogMessage("Starting vsperfmon: {0} {1}",
                                      info.FileName,
                                      info.Arguments);
                RunProcessAsynch(info);
                //wait a little and let perfmon start
                Thread.Sleep(2000);
            }
            catch (Win32Exception)
            {
                Log.LogError("Couldnt locate vsperfmon.exe. Check PerformanceToolsDir Setting");
                throw;
            }
        }

        private int ExecuteProgram()
        {
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = Path.Combine(this.OutputDirectory,Path.GetFileName(this.FileName));
            info.Arguments = this.Parmaters;
            Log.LogMessage("Executing Process: {0} {1}", info.FileName, info.Arguments);

            return RunProcessSynch(info);
        }

        private void StopPerfMonitor()
        {
            ProcessStartInfo info = new ProcessStartInfo();
            info.FileName = Path.Combine(this.PerformanceToolsDir, "vsperfcmd.exe");
            info.Arguments = "-shutdown";

            Log.LogMessage("Stopping vsperfmon: {0} {1}", info.FileName, info.Arguments);

            RunProcessSynch(info);
        }


        public override bool Execute()
        {
            if (!File.Exists(this.FileName))
            {
                Log.LogError("Can't locate Target Program: {0}", FileName);
                return false;
            }
            if (this.OutputDirectory==string.Empty)
            {
                Log.LogError("Output Path was not sepcified. Must specify directory to which write results to");
                return false;
            }

            InstrumentTarget();

            StartPerfMonitor();

            int testsExitCode = ExecuteProgram();

            StopPerfMonitor();

            return (testsExitCode == 0);

        }

        private void PrintToConsole(object sendingProcess,
            DataReceivedEventArgs outLine)
        {
            if (string.IsNullOrEmpty(outLine.Data))
                return;
            if (this.BuildEngine != null)
            {

                this.Log.LogMessage(outLine.Data);
            }
            else
            {
                Console.WriteLine(outLine.Data);
            }
        }

    }
}
